package wml.clipstore.storage.clips;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;

import wml.clipstore.base.Entity;
import wml.clipstore.base.Identifier;
import wml.clipstore.map.MapManager;
import wml.clipstore.utils.EntityUtils;
import CLIPSJNI.Environment;
import CLIPSJNI.FactAddressValue;
import CLIPSJNI.PrimitiveValue;
import CLIPSJNI.MultifieldValue;

class ClipsGetter {

	private MapManager manager;
	private Environment clips;

	public ClipsGetter(MapManager manager, Environment clips) {
		this.manager = manager;
		this.clips = clips;
	}

	public <T extends Entity> T getEntity(FactAddressValue fact) {
		HashMap<String, Object> getted = new HashMap<String, Object>();
		return this.getEntity(fact, getted);
	}

	public <T extends Entity> T getEntity(FactAddressValue fact, Class<T> clazz)
			throws Exception {
		HashMap<String, Object> getted = new HashMap<String, Object>();
		T entity = clazz.newInstance();
		this.loadEntity(fact, entity, getted);
		return entity;
	}

	public <T extends Collection<?>> T getCollection(
			FactAddressValue factAddress, Class<T> clazz) {
		HashMap<String, Object> getted = new HashMap<String, Object>();
		return this.getCollection(factAddress, clazz, getted);
	}

	private <T extends Entity> T getEntity(FactAddressValue fact,
			HashMap<String, Object> getted) {
		try {
			PrimitiveValue pv = fact.getFactSlot("identifier");
			String scls = pv.stringValue().split("@")[1];
			if (scls.endsWith("\""))
				scls = scls.substring(0, scls.length() - 1);
			@SuppressWarnings("unchecked")
			T e = (T) Class.forName(scls).newInstance();
			this.loadEntity(fact, e, getted);
			return e;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

	private <T extends Entity> void loadEntity(FactAddressValue fact, T entity,
			HashMap<String, Object> getted) throws Exception {
		List<Field> fields = this.manager.getStoredFields(entity.getClass());
		for (Field field : fields) {
			String sValue = fact.getFactSlot(field.getName()).toString();
			Object value = getValue(sValue, getted);
			try {
				field.setAccessible(true);
				field.set(entity, value);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	@SuppressWarnings({ "rawtypes" })
	private <T extends Collection> T getCollection(
			FactAddressValue factAddress, Class<T> clazz,
			HashMap<String, Object> getted) {
		try {
			T collection = (T) clazz.newInstance();
			loadCollection(factAddress, collection, getted);
			return collection;
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	@SuppressWarnings({ "unchecked", "rawtypes" })
	private <T extends Collection> void loadCollection(
			FactAddressValue factAddress, T collection,
			HashMap<String, Object> getted) {
		try {
			List list = factAddress.getFactSlot("items").multifieldValue();
			for (int i = 0; i < list.size(); i++) {
				String sValue = list.get(i).toString();
				Object value = this.getValue(sValue, getted);
				collection.add(value);
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private Object getValue(String sValue, HashMap<String, Object> getted) {
		try {
			int index = sValue.lastIndexOf("@");
			String className = null;
			if (sValue.endsWith("\"")) {
				className = sValue.substring(index + 1, sValue.length() - 1);
			} else {
				className = sValue.substring(index + 1, sValue.length());
			}
			Class<?> clazz = Class.forName(className);
			if (EntityUtils.isSubClass(clazz, Entity.class)) {
				Object entity = getted.get(sValue);
				if (entity != null) {
					return entity;
				}
				FactAddressValue factAddress = this.getFact(className,
						"identifier", new StringBuilder("\"").append(sValue)
								.append("@").append(Identifier.class.getName())
								.append("\"").toString());
				Entity et = (Entity) clazz.newInstance();
				et.setIdentifier(new Identifier(sValue));
				getted.put(sValue, et);
				loadEntity(factAddress, et, getted);
				return et;
			} else if (EntityUtils.isSubClass(clazz, Collection.class)) {
				Object entity = getted.get(sValue);
				if (entity != null) {
					return entity;
				}
				FactAddressValue factAddress = this.getFact("Collection",
						"identifier", new StringBuilder(sValue).toString());
				Collection<?> c = (Collection<?>) clazz.newInstance();
				getted.put(sValue, c);
				loadCollection(factAddress, c, getted);
				return c;
			} else {
				String value = sValue.substring(1, index);
				Constructor<?> constructor = clazz.getConstructor(String.class);
				return constructor.newInstance(value);
			}
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}

	public FactAddressValue getFact(String className, String slotName,
			String slotValue) {
		try {
			StringBuilder query = new StringBuilder("(find-fact ((?f ")
					.append(className).append(")) (eq ?f:").append(slotName)
					.append(" ").append(slotValue).append("))");
//			System.out.println(query.toString());
			MultifieldValue v = (MultifieldValue) clips.eval(query.toString());
			return (FactAddressValue) v.get(0);
		} catch (Exception e) {
			return null;
		}
	}

	@SuppressWarnings("unchecked")
	public List<FactAddressValue> getFacts(String className, String slotName,
			String slotValue) throws Exception {
		StringBuilder query = new StringBuilder("(find-all-facts ((?f ")
				.append(className).append(")) (eq ?f:").append(slotName)
				.append(" ").append(slotValue).append("))");
		return clips.eval(query.toString()).multifieldValue();
	}
}
